//go:build windows
// +build windows

package cpu

import (
	"go-cmd/internal/common"
	"unsafe"

	"golang.org/x/sys/windows"
)

var (
	modKernel32 = common.ModKernel32

	// U can see more infomation about this api at https://learn.microsoft.com/zh-cn/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsysteminfo
	procGetSystemInfo  = modKernel32.NewProc("GetSystemInfo")
	procGetSystemTimes = modKernel32.NewProc("GetSystemTimes")
)

type ARCH_TYPE uint16

//go:generate stringer -type=ARCH_TYPE
const (
	PROCESSOR_ARCHITECTURE_AMD64   ARCH_TYPE = 9
	PROCESSOR_ARCHITECTURE_ARM     ARCH_TYPE = 5
	PROCESSOR_ARCHITECTURE_ARM64   ARCH_TYPE = 12
	PROCESSOR_ARCHITECTURE_IA64    ARCH_TYPE = 6 // Intel Itanium
	PROCESSOR_ARCHITECTURE_INTEL   ARCH_TYPE = 0 // x86
	PROCESSOR_ARCHITECTURE_UNKNOWN ARCH_TYPE = 0xffff
)

type DWORD uint32
type WORD uint16
type PTR uintptr

type DUMMY_UNION_NAME struct {
	WProcessorArchitecture ARCH_TYPE
	WReserved              WORD
}

type SYSTEM_INFO struct {
	DUMMY_UNION_NAME
	DwPageSize                  DWORD
	lpMinimumApplicationAddress PTR
	lpMaximumApplicationAddress PTR
	DwActiveProcessorMask       PTR
	DwNumberOfProcessors        DWORD
	DwProcessorType             DWORD
	DwAllocationGranularity     DWORD
	WProcessorLevel             WORD
	WProcessorRevision          WORD
}

// It can get the system information, so can use it to get the architecture of cpu
func GetSystemInfo() (*SYSTEM_INFO, error) {
	var system_info SYSTEM_INFO
	_, _, err := procGetSystemInfo.Call((uintptr(unsafe.Pointer(&system_info))))
	if err != nil && err.Error() != "The operation completed successfully." {
		return nil, err
	}
	return &system_info, nil
}

func GetCPURunningTime() (*TimeStat, error) {
	var lpIdleTime, lpKernelTime, lpUserTime common.FILETIME
	r, _, err := procGetSystemTimes.Call(
		uintptr(unsafe.Pointer(&lpIdleTime)),
		uintptr(unsafe.Pointer(&lpKernelTime)),
		uintptr(unsafe.Pointer(&lpUserTime)),
	)
	if r == 0 {
		return nil, err
	}
	ret := &TimeStat{
		Idle:   uint64(lpIdleTime.DwHighDateTime)<<32 + uint64(lpIdleTime.DwLowDateTime),
		Kernel: uint64(lpKernelTime.DwHighDateTime)<<32 + uint64(lpKernelTime.DwLowDateTime),
		User:   uint64(lpUserTime.DwHighDateTime)<<32 + uint64(lpUserTime.DwLowDateTime),
	}
	ret.System = ret.Kernel - ret.Idle
	return ret, nil
}

func GetProcessCPUTime(pid int) (*TimeStat, error) {
	h, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pid))
	if err != nil {
		return nil, err
	}
	defer windows.CloseHandle(h)

	var lpCreateTime, lpExitTime, lpKernelTime, lpUserTime windows.Filetime
	err = windows.GetProcessTimes(h, &lpCreateTime, &lpExitTime, &lpKernelTime, &lpUserTime)
	if err != nil {
		return nil, err
	}
	return &TimeStat{
		User:   uint64(lpUserTime.HighDateTime)<<32 + uint64(lpUserTime.LowDateTime),
		Kernel: uint64(lpKernelTime.HighDateTime)<<32 + uint64(lpKernelTime.LowDateTime),
	}, nil
}
